package logic

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"litetalk/config"
	"litetalk/db"
	"litetalk/proto"
	"litetalk/tools"
	"strconv"
	"time"

	"github.com/go-redis/redis"
	"github.com/sirupsen/logrus"
)

func getUserKey(authKey string) string {
	var ret bytes.Buffer
	ret.WriteString(config.RedisPrefix)
	ret.WriteString(authKey)
	return ret.String()
}

var RedisClient *redis.Client
var RedisSessClient *redis.Client

func init() {
	Init()
}

func Init() {
	redisOpt := tools.RedisOption{
		Address:  config.Conf.Redis.RedisAddress,
		Password: config.Conf.Redis.RedisPassword,
		Db:       config.Conf.Redis.Db,
	}

	RedisClient = tools.GetRedisInstance(redisOpt)
	if pong, err := RedisClient.Ping().Result(); err != nil {
		logrus.Infof("RedisCli Ping Result pong: %s,  err: %s", pong, err)
	}
	RedisSessClient = RedisClient
}

type Logic struct{}

var LogicLocal Logic

func (*Logic) Register(args *proto.IMRegister, reply *proto.IMRegisterAck) error {
	reply.Code = proto.CodeFailed

	u := &db.IMUser{
		UserName:  args.UserName,
		Password:  tools.Sha1(args.Password),
		NickName:  args.NickName,
		DepartId:  args.DepartId,
		AvatarUrl: args.AvatarUrl,
		TelNumber: args.TelNumber,
	}

	if u.DepartId == 0 {
		depart := &db.IMDepart{}
		depart2 := depart.CheckHaveOrganize(args.OrganizeNumber)
		u.DepartId = depart2.ID
	}

	uData := u.CheckHaveUserName(args.UserName)
	if uData.ID > 0 {
		return errors.New("this user name already have, please login")
	}

	userId, err := u.Add()
	if err != nil {
		logrus.Infof("register err:%s", err.Error())
		return err
	}
	if userId == 0 {
		return errors.New("register userId empty")
	}
	reply.Code = proto.CodeSuccess
	return nil
}

func (*Logic) Login(args *proto.IMLoginReq, reply *proto.IMLoginRes) error {

	reply.Code = proto.CodeFailed

	data := db.User.CheckHaveUserName(args.UserName)
	if data.ID == 0 {
		return errors.New("no this user")
	}

	if args.Password != data.Password {
		return errors.New("password false")
	}

	loginSessId := tools.GetSessionIdByUserId(int(data.ID))
	randToken := tools.GetRandomToken(32)
	sessionId := tools.CreateSessionId(randToken)
	userData := make(tools.R)
	userData["userId"] = data.ID
	userData["userName"] = args.UserName

	token, _ := RedisSessClient.Get(loginSessId).Result()
	if token != "" {
		oldSession := tools.CreateSessionId(token)
		err := RedisSessClient.Del(oldSession).Err()
		if err != nil {
			return errors.New("logout user fail! token is:" + token)
		}
	}

	RedisSessClient.Do("MULTI")
	RedisSessClient.HMSet(sessionId, userData)
	RedisSessClient.Expire(sessionId, config.RedisValidTime)
	RedisSessClient.Set(loginSessId, randToken, config.RedisValidTime)
	err := RedisSessClient.Do("EXEC").Err()
	if err != nil {
		logrus.Infof("register set redis token fail!")
		return err
	}
	reply.Code = proto.CodeSuccess
	reply.Msg = proto.MsgSuccess
	reply.AuthToken = randToken
	reply.UserInfo = proto.IMUser{
		Id:        data.ID,
		DepartId:  data.DepartId,
		RealName:  data.UserName,
		NickName:  data.NickName,
		AvatarUrl: data.AvatarUrl,
		SignInfo:  data.SignInfo,
		TelNumber: data.TelNumber,
	}
	return nil
}

func (*Logic) Logout(args *proto.IMLogout, reply *proto.IMLogoutReply) error {
	reply.Code = proto.CodeFailed

	sessionName := tools.GetSessionName(args.AuthToken)
	userDataMap, err := RedisSessClient.HGetAll(sessionName).Result()
	if err != nil {
		logrus.Infof("check auth fail!, authToken is:%s", args.AuthToken)
		return err
	}

	if len(userDataMap) == 0 {
		logrus.Infof("no this user session,authToken is:%s", args.AuthToken)
		return nil
	}

	UserId, _ := strconv.Atoi(userDataMap["userId"])
	SessionId := tools.GetSessionIdByUserId(UserId)
	err = RedisSessClient.Del(SessionId).Err()
	if err != nil {
		logrus.Infof("logout del sess map error:%s", err.Error())
		return err
	}

	serverIdKey := getUserKey(fmt.Sprintf("%d", UserId))
	err = RedisSessClient.Del(serverIdKey).Err()
	if err != nil {
		logrus.Infof("logout del server id error:%s", err.Error())
		return err
	}

	err = RedisSessClient.Del(sessionName).Err()
	if err != nil {
		logrus.Infof("logout error:%s", err.Error())
		return err
	}

	reply.Code = proto.CodeSuccess
	return nil
}

func (*Logic) Push(args *proto.Msg, reply *proto.SuccessReply) error {
	var err error
	reply.Code = proto.CodeFailed

	err = RedisPublishChannel(args)
	if err != nil {
		logrus.Errorf("logic,redis publish err: %s", err.Error())
		return err
	}
	reply.Code = proto.CodeSuccess
	return nil
}

func (*Logic) Connect(args *proto.Connect, reply *proto.ConnectReply) error {

	sessionName := tools.GetSessionName(args.AuthToken)
	userInfo, err := RedisSessClient.HGetAll(sessionName).Result()
	if err != nil {
		logrus.Errorf("logic Connect invalid session ", err)
		return err
	}

	userId, _ := strconv.Atoi(userInfo["userId"])
	userName := userInfo["userName"]
	if userId == 0 {
		logrus.Errorf("logic Connect UserId is zero")
		return errors.New("connect error")
	}

	reply.UserId = uint(userId)
	reply.UserName = userName

	RedisSessClient.HSet(config.RedisOnline, strconv.Itoa(userId), userName)

	kick := &proto.IMKickUser{
		UserId: uint(userId),
	}
	body, _ := json.Marshal(kick)
	RedisPublishChannel(proto.NewMsg(proto.OpKickUserOnline, body, 0))
	return nil
}

func (*Logic) DisConnect(args *proto.DisConnect, reply *proto.DisConnectReply) error {
	reply.Has = false
	sessionName := tools.GetSessionName(args.AuthToken)
	userInfo, err := RedisSessClient.HGetAll(sessionName).Result()
	if err != nil {
		logrus.Errorf("logic DisConnect invalid session ", err)
		return err
	}

	userIdStr := userInfo["userId"]
	userId, _ := strconv.Atoi(userIdStr)
	if userId == 0 {
		return errors.New("logic DisConnect invalid userId")
	}

	err = RedisSessClient.HDel(config.RedisOnline, userIdStr).Err()
	if err != nil {
		return err
	}

	kick := &proto.IMKickUser{
		UserId: uint(userId),
	}
	body, _ := json.Marshal(kick)
	RedisPublishChannel(proto.NewMsg(proto.OpKickUserOffline, body, 0))
	reply.Has = true
	return nil
}

func (*Logic) OnlineUserList(args *proto.OnlineUserReq, reply *proto.OnlineUserRep) error {
	userInfo, err := RedisSessClient.HGetAll(config.RedisOnline).Result()
	if err != nil {
		logrus.Errorf("logic OnlineUserList ", err)
		return err
	}

	reply.UserIdList = make([]uint, 0, len(userInfo))

	for key := range userInfo {
		val, _ := strconv.Atoi(key)
		reply.UserIdList = append(reply.UserIdList, uint(val))
	}
	return nil
}

func (*Logic) Department(args *proto.IMDepartReq, reply *proto.IMDepartRes) error {
	reply.Code = proto.CodeFailed

	user := db.User.CheckHaveUserId(args.UserId)
	depart := db.Depart.CheckHaveDepartId(user.DepartId)
	reply.DepartList = make([]proto.DepartInfo, 0)

	for {
		if depart.ID == 0 {
			break
		}
		reply.DepartList = append(reply.DepartList, proto.DepartInfo{
			Id:       depart.ID,
			Name:     depart.Name,
			ParentId: depart.ParentId,
		})

		if depart.ParentId == 0 {
			break
		}

		depart = db.Depart.CheckHaveDepartId(depart.ParentId)
	}

	reply.Code = proto.CodeSuccess
	return nil
}

func (*Logic) AllUser(args *proto.IMAllUserReq, reply *proto.IMAllUserRes) error {
	reply.Code = proto.CodeFailed

	lastTime := time.Unix(args.LastTime, 0)

	users := db.User.GetAllUser(lastTime)

	reply.UserList = make([]proto.IMUser, 0, len(users))

	for _, user := range users {
		reply.UserList = append(reply.UserList, proto.IMUser{
			Id:        user.ID,
			DepartId:  user.DepartId,
			RealName:  user.UserName,
			NickName:  user.NickName,
			AvatarUrl: user.AvatarUrl,
			SignInfo:  user.SignInfo,
			TelNumber: user.TelNumber,
		})
	}

	reply.Code = proto.CodeSuccess
	return nil
}

func (*Logic) CheckAuth(args *proto.CheckAuthReq, reply *proto.CheckAuthRep) error {
	reply.Code = proto.CodeFailed
	autoToken := args.AuthToken
	sessionName := tools.GetSessionName(autoToken)
	userDataMap, err := RedisSessClient.HGetAll(sessionName).Result()
	if err != nil {
		logrus.Errorf("check auth fail!, autoToken is:%s", autoToken)
		return err
	}
	if len(userDataMap) == 0 {
		logrus.Errorf("no this user seesion, authToken is:%s", autoToken)
		return nil
	}
	userId, _ := strconv.Atoi(userDataMap["userId"])
	reply.UserId = uint(userId)
	reply.UserName = userDataMap["userName"]
	reply.Code = proto.CodeSuccess
	return nil
}

func (*Logic) NormalGroupList(args *proto.IMNormalGroupListReq, reply *proto.IMNormalGroupListRes) error {
	igms := db.GroupMember.GetDataByUserId(args.UserId)
	reply.Code = proto.CodeSuccess
	reply.GroupIdList = make([]uint, 0)
	for _, data := range igms {
		reply.GroupIdList = append(reply.GroupIdList, data.GroupId)
	}
	return nil
}

func (*Logic) GroupsInfo(args *proto.IMGroupInfoListReq, reply *proto.IMGroupInfoListRes) error {

	reply.GroupInfoList = make([]proto.GroupInfo, 0)
	for _, groupId := range args.GroupIdList {
		group := &db.IMGroup{}
		group.GetDataById(groupId)
		imGroupMember := &db.IMGroupMember{}
		members := imGroupMember.GetDataByGroupId(groupId)
		memberIdList := make([]uint, 0, len(members))
		for _, member := range members {
			memberIdList = append(memberIdList, member.UserId)
		}

		reply.GroupInfoList = append(reply.GroupInfoList, proto.GroupInfo{
			GroupId:        group.ID,
			GroupName:      group.Name,
			GroupAvatar:    group.Avatar,
			GroupCreatorId: group.CreaterId,
			MemberId:       memberIdList,
		})
	}

	reply.Code = proto.CodeSuccess
	return nil
}

func (*Logic) CreateGroup(args *proto.IMGroupCreateReq, reply *proto.IMGroupCreateRes) error {
	reply.Code = proto.CodeFailed

	group := &db.IMGroup{
		CreaterId: args.UserId,
		Name:      args.GroupName,
		Avatar:    args.GroupAvatar,
	}
	groupId, err := group.Add()
	if err != nil {
		logrus.Error("create group error name=%s err=%s", args.GroupName, err)
		return err
	}

	for _, memberId := range args.MemberIdList {
		groupMember := &db.IMGroupMember{
			GroupId: groupId,
			UserId:  memberId,
		}
		groupMember.Add()
	}

	notify := &proto.IMGroupChangeMemberNotify{
		GroupId:           groupId,
		UserId:            args.UserId,
		ChangeType:        proto.CID_OK,
		CurrentUserIdList: args.MemberIdList,
		ChangeUserIdLIst:  args.MemberIdList,
	}
	reply.Code = proto.CodeSuccess
	reply.GroupId = groupId
	body, _ := json.Marshal(notify)
	RedisPublishChannel(proto.NewMsg(proto.OpGroupChangeMemberNotify, body, 0))
	return nil
}

func (*Logic) GroupChangeMember(args *proto.IMGroupChangeMemberReq, reply *proto.IMGroupChangeMemberRes) error {

	group, err := db.Group.GetDataById(args.GroupId)
	if err != nil {
		return err
	}

	for _, memberId := range args.MemberIdList {
		imGroupMember := &db.IMGroupMember{
			GroupId: group.ID,
			UserId:  memberId,
		}
		if args.ChangeType == proto.CID_OK {
			imGroupMember.Add()
		} else {
			imGroupMember.Del()
		}
	}
	gmList := db.GroupMember.GetDataByGroupId(args.GroupId)
	notify := &proto.IMGroupChangeMemberNotify{
		GroupId:          args.GroupId,
		UserId:           args.UserId,
		ChangeType:       args.ChangeType,
		ChangeUserIdLIst: args.MemberIdList,
	}
	notify.CurrentUserIdList = make([]uint, len(gmList))

	for _, tmp := range gmList {
		notify.CurrentUserIdList = append(notify.CurrentUserIdList, tmp.UserId)
	}

	data, _ := json.Marshal(notify)
	msg := proto.NewMsg(proto.OpGroupChangeMemberNotify, data, 0)
	return RedisPublishChannel(msg)
}

func (*Logic) GetUserInfo(args *proto.IMUserInfoReq, reply *proto.IMUserInfoRes) error {
	infoList := db.User.GetUserList(args.UserIdList)

	reply.UserInfoList = make([]proto.IMUser, len(infoList))
	for _, info := range infoList {
		reply.UserInfoList = append(reply.UserInfoList, proto.IMUser{
			Id:        info.ID,
			DepartId:  info.DepartId,
			NickName:  info.NickName,
			RealName:  info.UserName,
			AvatarUrl: info.AvatarUrl,
			TelNumber: info.TelNumber,
			SignInfo:  info.SignInfo,
		})
	}
	return nil
}

func (*Logic) GetUserInfoByName(args *proto.IMUserInfoNameReq, reply *proto.IMUserInfoNameRes) error {
	user := db.User.CheckHaveUserName(args.UserName)
	if user.ID == 0 {
		return fmt.Errorf("no this user")
	}

	reply.Data = proto.IMUser{
		Id:        user.ID,
		DepartId:  user.DepartId,
		NickName:  user.NickName,
		RealName:  user.UserName,
		AvatarUrl: user.AvatarUrl,
		TelNumber: user.TelNumber,
		SignInfo:  user.SignInfo,
	}
	return nil
}

func (*Logic) GetUserStat(args *proto.IMUserStatReq, reply *proto.IMUserStatRes) error {
	return nil
}

func (*Logic) ChangeAvatar(args *proto.IMChangeAvatarReq, reply *proto.IMChangeAvatarRes) error {
	user := &db.IMUser{}
	u := user.CheckHaveUserId(args.ThisUserId)
	if u.ID == 0 {
		reply.Code = 1
		reply.ThisUserId = args.ThisUserId
		return fmt.Errorf("no this user %d", args.ThisUserId)
	}

	u.AvatarUrl = args.AvatarUrl
	u.Save()
	reply.Code = 0
	reply.ThisUserId = args.ThisUserId

	notify := proto.IMChangeAvatarNotify{
		UserId:    args.ThisUserId,
		AvatarUrl: args.AvatarUrl,
	}

	body, _ := json.Marshal(notify)
	msg := proto.NewMsg(proto.OpChangeAvatarNotify, body, 0)
	RedisPublishChannel(msg)
	return nil
}

func (*Logic) ChangeSignInfo(args *proto.IMChangeSignInfoReq, reply *proto.IMChangeSignInfoRes) error {
	user := &db.IMUser{}
	u := user.CheckHaveUserId(args.ThisUserId)
	if u.ID == 0 {
		reply.Code = 1
		reply.ThisUserId = args.ThisUserId
		return fmt.Errorf("no this user %d", args.ThisUserId)
	}

	u.SignInfo = args.SignInfo
	u.Save()
	reply.Code = 0
	reply.ThisUserId = args.ThisUserId

	notify := proto.IMChangeSignInfoNotify{
		ThisUserId: args.ThisUserId,
		SignInfo:   args.SignInfo,
	}

	body, _ := json.Marshal(notify)
	msg := proto.NewMsg(proto.OpChangeSignInfoNotify, body, 0)
	RedisPublishChannel(msg)
	return nil
}

func (*Logic) ChangePassword(args *proto.IMChangePassword, reply *proto.SuccessReply) error {
	u := db.User.CheckHaveUserName(args.UserName)
	if u.ID == 0 {
		reply.Code = 1
		return fmt.Errorf("no user %s", args.UserName)
	}

	reply.Code = 0
	u.Password = tools.Sha1(args.Password)
	u.Save()
	return nil
}

func (*Logic) DeleteUser(args *proto.IMDeleteUser, reply *proto.SuccessReply) (err error) {

	err = db.User.DeleteUser(args.UserNameList)
	if err != nil {
		reply.Code = 1
		reply.Msg = err.Error()
	} else {
		reply.Code = 0
	}
	return
}

func (*Logic) ChangeGroupAvatar(args *proto.IMGroupChangeAvatarReq, reply *proto.IMGroupChangeAvatarRes) error {
	reply.Code = 1
	group, err := db.Group.GetDataById(args.GroupId)
	if err != nil {
		return err
	}
	reply.Code = 0
	group.Avatar = args.AvatarUrl
	group.Save()

	notify := proto.IMGroupChangeAvatarNotify{}
	body, _ := json.Marshal(notify)
	msg := proto.NewMsg(proto.OpGroupChangeAvatar, body, 0)
	RedisPublishChannel(msg)
	return nil
}

func (*Logic) CreateDepart(args *proto.IMDepartInfo, reply *proto.IMDepartInfoAck) error {
	reply.Code = 1

	info := db.Depart.CheckHaveDepartName(args.Name)
	if info.ID != 0 {
		reply.Id = info.ID
		return fmt.Errorf("already has depart %s", info.Name)
	}

	if args.ParentOrganize != "" && args.ParentOrganize != "0" {
		parent := db.Depart.CheckHaveOrganize(args.ParentOrganize)
		info.ParentId = parent.ID
	}

	info.Name = args.Name
	info.Organize = args.Organize

	reply.Code = 0
	reply.Id, _ = info.Add()
	return nil
}

func (*Logic) ChangeDepartInfoByNumber(args *proto.IMDepartInfo, reply *proto.IMDepartInfoAck) error {
	reply.Code = 1

	info := db.Depart.CheckHaveOrganize(args.Organize)
	if info.ID == 0 {
		return fmt.Errorf("no department number %s", args.Organize)
	}

	if args.ParentOrganize != "" {
		parent := db.Depart.CheckHaveOrganize(args.ParentOrganize)
		info.ParentId = parent.ID
	}

	info.Name = args.Name
	info.Save()

	reply.Code = 0
	reply.Id = info.ID
	return nil
}

func (*Logic) DeleteDepart(args *proto.IMDepartOrganize, reply *proto.SuccessReply) error {
	reply.Code = 1
	info := db.Depart.CheckHaveOrganize(args.Organize)
	if info.ID == 0 {
		return fmt.Errorf("depart not organize %s", args.Organize)
	}
	info.Delete()
	reply.Code = 0
	return nil
}

func (*Logic) GetMessageList(args *proto.IMGetMsgListReq, reply *proto.IMGetMsgListRes) error {

	reply.ThisUserId = args.ThisUserId
	msgList := db.Message.CheckFromId(uint(args.MsgIdBegin), args.ThisUserId, args.SessionId, args.MsgCount)
	for _, msg := range msgList {
		reply.MsgInfoList = append(reply.MsgInfoList, proto.MsgInfo{
			MsgId:           msg.ID,
			FromSessionId:   msg.FromId,
			FromSessionType: args.SessionType,
			CreateTime:      msg.CreatedAt.Unix(),
			MsgData:         msg.Content,
		})
	}

	return nil
}

func (*Logic) GetLastestMessage(args *proto.IMGetLastestMsgIdReq, reply *proto.IMGetLastestMsgIdRes) error {
	reply.UserId = args.UserId
	reply.SessionId = args.SessionId
	reply.SessionType = args.SessionType
	reply.LastestMsgId = db.Message.CheckLastMsg(args.UserId, args.SessionId).ID
	return nil
}

func (*Logic) GetMessageById(args *proto.IMGetMsgByIdReq, reply *proto.IMGetMsgByIdRes) error {
	reply.UserId = args.UserId
	reply.SessionType = args.SessionType
	reply.SessionId = args.SessionId

	msgs := db.Message.CheckById(args.MsgIdList)

	for _, msg := range msgs {
		reply.MsgInfoList = append(reply.MsgInfoList, proto.MsgInfo{
			MsgId:           msg.ID,
			FromSessionId:   msg.FromId,
			FromSessionType: 0,
			CreateTime:      msg.CreatedAt.Unix(),
			MsgData:         msg.Content,
		})
	}
	return nil
}
